/*
 * Copyright ©2015-2021 Jaemon. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gitee.jaemon.spring.adorner.util;

import io.gitee.jaemon.spring.adorner.annotation.FieldNote;
import io.gitee.jaemon.spring.adorner.core.DocContainer;
import io.gitee.jaemon.spring.adorner.type.AnnotationPair;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

import static io.gitee.jaemon.spring.adorner.AdornerConstants.*;
import static io.gitee.jaemon.spring.adorner.support.MarkdownHelper.*;
import static io.gitee.jaemon.spring.adorner.util.MarkdownUtils.LINE_BREAK;

/**
 * Adorner工具类
 *
 * @author Jaemon
 * @since 1.0
 */
public class AdornerUtils {

    /**
     * 类对应的参数表
     *
     * @param clazz
     *      类类型
     * @return
     *      类对应的参数表
     */
    public static String paramTable(Class<?> clazz) {
        return paramTable(clazz, new ArrayList<>());
    }

    /**
     * 类对应的参数表
     *
     * @param clazz
     *      类类型
     * @param handled
     *      处理过字段类集合
     * @return
     *      类对应的参数表
     */
    public static String paramTable(Class<?> clazz, List<String> handled) {
        if (clazz == null) {
            return EMPTY_TABLE;
        }
        List<Field> fields = ClassUtils.fields(clazz);
        if (fields.isEmpty()) {
            return EMPTY_TABLE;
        }

        String className = clazz.getName() + ClassUtils.SPOT;
        StringBuilder result = new StringBuilder(TABLE_HEAD);
        Map<String, String> innerMap = new HashMap<>();

        for (Field field : fields) {
            Class<?> type = field.getType();
            Type genericType = field.getGenericType();
            boolean generic = !field.getType().getName().equals(genericType.getTypeName());
            Class<?> componentType = type.getComponentType();
            boolean ignoreType = AdornerUtils.ignoreType(type) || generic;

            String typeName = generic ? genericType.getTypeName() : field.getType().getSimpleName();
            if (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)) {
                typeName = typeName(field, innerMap, handled);
            } else if (!ignoreType) {
                putIfAbsent(innerMap, handled, type);
                typeName = field.getType().getSimpleName();
            } else if (componentType != null) {
                // 自定义类型
                if (!AdornerUtils.ignoreType(componentType)) {
                    putIfAbsent(innerMap, handled, componentType);
                }
                typeName = componentType.getSimpleName() + "[]";
            }

            String fieldName = field.getName();
            boolean required = true;
            String fieldNotes = DocContainer.CONTAINER.fieldGet(className + fieldName, fieldName);
            if (field.isAnnotationPresent(FieldNote.class)) {
                FieldNote fieldNote = field.getAnnotation(FieldNote.class);
                required = fieldNote.required();
                fieldNotes = fieldNote.value();
            }

            result.append(
                    String.format(
                            CONTENT_FORMAT, fieldName, typeName, required ? "Y" : "N", fieldNotes
                    )
            );
        }

        if (!innerMap.isEmpty()) {
            innerMap.forEach((k , v) -> {
                result.append(LINE_BREAK).append(k).append(LINE_BREAK);
                result.append(v);
            });
        }
        return result.toString();
    }


    public static String typeName(Field field, Map<String, String> innerMap, List<String> inners) {
        Class<?> type = field.getType();
        Type genericType = field.getGenericType();
        String typeName = type.getSimpleName();
        if (ParameterizedType.class.isInstance(genericType)) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            if (Collection.class.isAssignableFrom(type)) {
                if (actualTypeArguments.length == 1) {
                    Class<?> actualTypeArgument = (Class<?>) actualTypeArguments[0];
                    putIfAbsent(innerMap, inners, actualTypeArgument);
                    typeName = typeName + "<"+ actualTypeArgument.getSimpleName() +">";
                }
            } else if (Map.class.isAssignableFrom(type)) {
                String types = Arrays.stream(actualTypeArguments).map(e -> {
                    Class<?> actualTypeArgument = (Class<?>) e;
                    putIfAbsent(innerMap, inners, actualTypeArgument);
                    return actualTypeArgument.getSimpleName();
                }).collect(Collectors.joining(COMMA));
                return typeName + "<" + types + ">";
            }

        }
        return typeName;
    }

    public static void putIfAbsent(Map<String, String> innerMap, List<String> inners, Class<?> type) {
        if (ignoreType(type)) {
            return;
        }
        if (!inners.contains(type.getName())) {
            innerMap.put(type.getSimpleName(), paramTable(type, inners));
            inners.add(type.getName());
        }
    }

    /**
     * 忽略处理的类型
     *
     * @param type
     *      {@link Class}
     * @return
     *      true | false
     */
    public static boolean ignoreType(Class<?> type) {
        return type.isPrimitive() ||
                Number.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) ||
                Character.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) ||
                Boolean.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type) ||
                Collection.class.isAssignableFrom(type);
    }

    /**
     * 方法是否被指定注解修饰, 并返回注解信息
     *
     * @param method
     *      方法对象
     * @param except
     *      期望注解
     * @param <T>
     *     注解
     * @return
     *      AnnotationPair {@link AnnotationPair}
     */
    public static <T extends Annotation> AnnotationPair<T> hasAnnotation(Method method, Class<T> except) {
        for (Annotation annotation : method.getAnnotations()) {
            T t = AnnotationUtils.contains(new Annotation[]{annotation}, except);
            if (t != null) {
                return AnnotationPair.of(annotation, t);
            }
        }
        return null;
    }

    public static String adornerProperties(String key, String defaultValue) {
        String value = System.getProperty(key) == null ? defaultValue : System.getProperty(key);
        LoggerHelper.debug("-D%s: %s.", key, value);
        return value;
    }


    public static <T> List<T> newArrayList(T...elements) {
        List<T> list = new ArrayList<>();
        for (T element : elements) {
            list.add(element);
        }
        return list;
    }

}